/*
* GPL
* Free soft,no warranted
* We are all in the mud, but some of us are looking at the stars.
* We are not born for ourselves.
*/
/**
* @file server.hpp
* @Synopsis
* @author Chai JunJun
* @date 2009-07-10
*/

#ifndef QSERVER_SERVER_HPP__
#define QSERVER_SERVER_HPP__

#include <boost/assert.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>

#include "io_service_pool.hpp"
#include "netconnection.hpp"
#include "netconnectionpool.hpp"

namespace qserv 
{

    /// The top-level class of the server.
    template<typename Work_Handler, typename Work_Allocator, typename Socket_Service = boost::asio::ip::tcp::socket>
    class server
        : private boost::noncopyable
    {
    public:
        /// The type of the NetConnection.
        typedef NetConnection<Work_Handler, Socket_Service> service_connection_type;
        typedef boost::shared_ptr< service_connection_type > service_connection_ptr;

        /// The type of the NetConnPool.
        typedef NetConnPool<Work_Handler, Work_Allocator, Socket_Service> service_connection_pool_type;
        typedef boost::shared_ptr< service_connection_pool_type > service_connection_pool_ptr;

        /// Construct the server to listen on the specified TCP address and port.
        explicit server(const std::string& address,
            unsigned short port,
            std::size_t io_service_pool_size,
            std::size_t work_service_pool_size,
            service_connection_pool_ptr service_handler_pool)
            : m_acceptServicePool(1),
            m_ioServicePool(io_service_pool_size),
            m_workServicePool(work_service_pool_size),
            m_netConnectionPool(service_handler_pool),
            m_acceptor(m_acceptServicePool.get_io_service()),
            m_endpoint(boost::asio::ip::address::from_string(address), port),
            m_started(false)
        {
            BOOST_ASSERT(service_handler_pool != 0);
            
        }

        /// Destruct the server object.
        ~server()
        {
            // Stop the server's io_service loop.
            stop();
            // Destroy service_handler pool.
            m_netConnectionPool.reset();
        }

        /// Run the server's io_service loop.
        void run()
        {
            if (m_started)
                return;

            // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
            m_acceptor.open(m_endpoint.protocol());
            m_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
            m_acceptor.bind(m_endpoint);
            m_acceptor.listen();
            // Accept new connection.
            accept_one();

            // Start work_service_pool with nonblock to perform synchronous works.
            m_workServicePool.start();
            // Start io_service_pool with nonblock to perform asynchronous i/o operations.
            m_ioServicePool.start();

            m_started = true;
            // Start accept_service_pool with block to perform asynchronous accept operations.
            m_acceptServicePool.run();
            
            // Stop io_service_pool.
            m_ioServicePool.stop();
            // Stop work_service_pool.
            m_workServicePool.stop();

            // Dispatch ready handlers.
            m_workServicePool.start();
            m_ioServicePool.start();
            m_ioServicePool.stop();
            m_workServicePool.stop();

            m_started = false;
        }

        /// Stop the server.
        void stop()
        {
            if (!m_started)
                return;

            // Close the acceptor in the same thread.
            m_acceptor.get_io_service().dispatch(boost::bind(&boost::asio::ip::tcp::acceptor::close,
                &m_acceptor));

            // Stop accept_service_pool from block.
            m_acceptServicePool.stop();
        }

    private:
        /// Start to accept one connection.
        void accept_one()
        {
            // Get new NetConnection for accept, and bind with acceptor's io_service.
            service_connection_ptr handler = m_netConnectionPool->getServConn(m_ioServicePool.get_io_service(),
                m_workServicePool.get_io_service());

            boost::function<void (service_connection_ptr)> cmdConnectionMade(boost::bind(&server::addConnections, this, _1));
            boost::function<void (service_connection_ptr)> cmdConnectionCancel(boost::bind(&server::removeConnection, this, _1));

            handler->hookConnectionMade(cmdConnectionMade);
            handler->hookConnectionCancel(cmdConnectionCancel);
            // Use new NetConnection to accept.
            m_acceptor.async_accept(handler->socket().lowest_layer(),
                boost::bind(&server::handle_accept,
                this,
                boost::asio::placeholders::error,
                handler));
        }

        /// Handle completion of an asynchronous accept operation.
        void handle_accept(const boost::system::error_code& e,
            service_connection_ptr handler)
        {
            if (!e)
            {
                // Start the first operation of the current handler.
                handler->start();
                // Accept new connection.
                accept_one();
            }
            else
                handler->close(e);
        }

        void addConnections(service_connection_ptr conn)
        {
            m_mutex.lock();
            m_connections.push_back(conn);
            m_mutex.unlock();
        }

        void removeConnection(service_connection_ptr conn)
        {
            m_mutex.lock();
            m_connections.remove(conn);
            std::cout << m_connections.size() << std::endl;
            m_mutex.unlock();
            sendToAllClients();
        }

        void sendToAllClients()
        {
            m_mutex.lock();
            boost::format fmt("There is %1% clinets in classroom\n");
            fmt % m_connections.size();
            m_buf = fmt.str();
            service_connection_ptr conn;
            boost::shared_ptr<event> evtptr(new event(event::user, m_buf.length(), (char *)m_buf.c_str()));
            BOOST_FOREACH(conn, m_connections)
            {
                conn->async_write(evtptr);
            }
            m_mutex.unlock();
        }
    private:
        /// The pool of io_service objects used to perform asynchronous accept operations.
        io_service_pool m_acceptServicePool;

        /// The pool of io_service objects used to perform asynchronous i/o operations.
        io_service_pool m_ioServicePool;

        /// The pool of io_service objects used to perform synchronous works.
        io_service_pool m_workServicePool;

        /// The pool of NetConnection objects.
        service_connection_pool_ptr m_netConnectionPool;

        /// The acceptor used to listen for incoming connections.
        boost::asio::ip::tcp::acceptor m_acceptor;

        /// The server endpoint.
        boost::asio::ip::tcp::endpoint m_endpoint;

        // Flag to indicate that the server is started or not.
        bool m_started;

        std::list< service_connection_ptr > m_connections;

        boost::asio::detail::mutex m_mutex;

        string m_buf;
    };

} // namespace qserv

#endif // QSERVER_SERVER_HPP__
